package com.partsoft.spring.zks;

import java.util.*;

import org.zkoss.zk.ui.*;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zkplus.databind.DataBinder;

@SuppressWarnings({"rawtypes", "unchecked"})
public class AnnotateDataBinder extends DataBinder {

	private static final long serialVersionUID = 8183335789960722526L;
	
	public static final String ARG_DATABINDER_REGISTED = "__databinder_registed__";  

	public AnnotateDataBinder() {
	}

	public AnnotateDataBinder(Desktop desktop) {
		init(desktop, true);
	}

	public AnnotateDataBinder(Page page) {
		init(page, true);
	}

	public AnnotateDataBinder(Component comp) {
		init(comp, true);
	}

	public AnnotateDataBinder(Component comps[]) {
		init(comps, true);
	}

	public AnnotateDataBinder(Desktop desktop, boolean defaultConfig) {
		init(desktop, defaultConfig);
	}

	public AnnotateDataBinder(Page page, boolean defaultConfig) {
		init(page, defaultConfig);
	}

	public AnnotateDataBinder(Component comps[], boolean defaultConfig) {
		init(comps, defaultConfig);
	}

	public AnnotateDataBinder(Component comp, boolean defaultConfig) {
		init(comp, defaultConfig);
	}

	public void init(Desktop desktop, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (Iterator it = desktop.getComponents().iterator(); it.hasNext(); loadAnnotations((Component) it
				.next()))
			;
	}

	public void init(Page page, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (Iterator it = page.getRoots().iterator(); it.hasNext(); loadAnnotations((Component) it
				.next()))
			;
	}

	public void init(Component comps[], boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		
		for (int j = 0; j < comps.length; ++j) {
			if (comps[j].getAttribute(ARG_DATABINDER_REGISTED) != null) continue;
			final List children = comps[j].getChildren();
			for (final Iterator it = children.iterator(); it.hasNext(); ) {
				loadAnnotations((Component) it.next()); //recursive back
			}
			comps[j].setAttribute(ARG_DATABINDER_REGISTED, true);
		}
	}

	public void init(Component comp, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		if (comp.getAttribute(ARG_DATABINDER_REGISTED) != null) return;
		final List children = comp.getChildren();
		for (final Iterator it = children.iterator(); it.hasNext(); ) {
			loadAnnotations((Component) it.next()); 
		}
		comp.setAttribute(ARG_DATABINDER_REGISTED, true);
	}

	private void loadAnnotations(Component comp) {
		loadComponentAnnotation(comp);
		loadComponentPropertyAnnotation(comp);
		List children = comp.getChildren();
		for (Iterator it = children.iterator(); it.hasNext(); loadAnnotations((Component) it.next()));
	}

	private void loadComponentPropertyAnnotation(Component comp) {
		loadComponentPropertyAnnotationByAnnotName(comp, "default");
		loadComponentPropertyAnnotationByAnnotName(comp, "bind");
	}

	private void loadComponentPropertyAnnotationByAnnotName(Component comp,
			String annotName) {
		ComponentCtrl compCtrl = (ComponentCtrl) comp;
		List props = compCtrl.getAnnotatedPropertiesBy(annotName);
		String propName;
		Object objs[];
		for (Iterator it = props.iterator(); it.hasNext(); addBinding(comp,
				propName, (String) objs[0], (List) objs[1], (List) objs[2],
				(String) objs[3], (String) objs[4], (Map) objs[5],
				(List) objs[6], (List) objs[7])) {
			propName = (String) it.next();
			objs = loadPropertyAnnotation(comp, propName, annotName);
		}

	}

	private void loadComponentAnnotation(Component comp) {
		loadComponentAnnotation(comp, "default");
		loadComponentAnnotation(comp, "bind");
	}

	private void loadComponentAnnotation(Component comp, String annotName) {
		ComponentCtrl compCtrl = (ComponentCtrl) comp;
		Annotation ann = compCtrl.getAnnotation(null, annotName);
		if (ann != null) {
			Map attrs = ann.getAttributes();
			String attr;
			List expr;
			List loadWhenEvents;
			List saveWhenEvents;
			List loadAfterEvents;
			List saveAfterEvents;
			String access;
			String converter;
			Map args;
			List tags;
			for (Iterator it = attrs.entrySet().iterator(); it.hasNext(); addBinding(
					comp, attr, (String) expr.get(0), loadWhenEvents,
					saveWhenEvents, access, converter, args, loadAfterEvents,
					saveAfterEvents)) {
				java.util.Map.Entry me = (java.util.Map.Entry) it.next();
				attr = (String) me.getKey();
				expr = parseExpression((String) me.getValue(), ";");
				if (expr == null || expr.get(0) == null)
					throw new UiException(
							"Cannot find any bean value in the annotation <a:bind "
									+ attr + "=\"\"/> for component " + comp
									+ ", id=" + comp.getId());
				tags = parseExpression((String) expr.get(0), ":");
				if (tags.size() > 1)
					throw new UiException(
							"bean value must be defined as the first statement in the annotation <a:bind "
									+ attr + "=\"\"/> for component " + comp
									+ ", id=" + comp.getId());
				loadWhenEvents = null;
				saveWhenEvents = null;
				loadAfterEvents = null;
				saveAfterEvents = null;
				access = null;
				converter = null;
				args = null;
				for (int j = 1; j < expr.size(); j++) {
					tags = parseExpression((String) expr.get(j), ":");
					if (tags == null)
						continue;
					if ("converter".equals(tags.get(0))) {
						converter = tags.size() <= 1 ? "none" : (String) tags
								.get(1);
						continue;
					}
					if ("save-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null)
							saveWhenEvents = parseExpression(
									(String) tags.get(1), ",");
						else
							saveWhenEvents.add("none");
						continue;
					}
					if ("load-after".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null)
							loadAfterEvents = parseExpression(
									(String) tags.get(1), ",");
						else
							loadAfterEvents.add("none");
						continue;
					}
					if ("load-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null)
							loadWhenEvents = parseExpression(
									(String) tags.get(1), ",");
						else
							loadWhenEvents.add("none");
						continue;
					}
					if ("access".equals(tags.get(0))) {
						access = tags.size() <= 1 ? "none" : (String) tags
								.get(1);
						continue;
					}
					if ("save-after".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null)
							saveAfterEvents = parseExpression(
									(String) tags.get(1), ",");
						else
							saveAfterEvents.add("none");
						continue;
					}
					if (args == null)
						args = new HashMap(1);
					args.put(tags.get(0), tags.get(1));
				}

				if (loadWhenEvents != null && loadWhenEvents.isEmpty())
					loadWhenEvents = null;
				if (saveWhenEvents != null && saveWhenEvents.isEmpty())
					saveWhenEvents = null;
				if (loadAfterEvents != null && loadAfterEvents.isEmpty())
					loadAfterEvents = null;
				if (saveAfterEvents != null && saveAfterEvents.isEmpty())
					saveAfterEvents = null;
			}

		}
	}

	static List parseExpression(String expr, String separator) {
		if (expr == null) {
			return null;
		}
		List results = new ArrayList(6);
		while (true) {
			int j = expr.indexOf(separator);
			if (j < 0) {
				results.add(expr.trim());
				return results;
			}
			results.add(expr.substring(0, j).trim());

			if (expr.length() <= (j + 1)) {
				return results;
			}
			expr = expr.substring(j + 1);
		}
	}
}
